#if !defined(MAP_EVALUATE_ADAPTER_H_)
#define MAP_EVALUATE_ADAPTER_H_

#include <boost/json.hpp>
#include <filesystem>
#include <string>

#include "filter/map_filter.h"
#include "map_decoder/map_decoder.h"

namespace autodrive {
namespace perception {

static constexpr std::int32_t kMaxMapHeadDatumNumber{8};

/// @brief head data item
struct HeadDatum {
  /// @brief class
  float *cls;
  /// @brief regression
  float *reg;
};

/// @brief map head output raw data
struct alignas(4) MapHeadDataRaw {
  /// @brief time stamp
  std::int64_t timestamp;
  /// @brief data
  HeadDatum data[kMaxMapHeadDatumNumber];
};

void write_matrix(Eigen::MatrixXd const &matrix,
                  std::filesystem::path const &file_path) noexcept(false);

/// @brief run map decode and post
/// @param jSettings [IN]json settings
/// @param dataRaw [IN]map head data
/// @param jMap [OUT] jMap
/// @param err [OUT] error message
/// @return success return true otherwise return false
extern "C" std::int32_t run_map_decode_and_post(char const *settings,
                                                MapHeadDataRaw const &data,
                                                double const *pose, char *map,
                                                char *err);

class MapEvaluateAdapter final {
 public:
  MapEvaluateAdapter(MapEvaluateAdapter &&) = delete;
  MapEvaluateAdapter(const MapEvaluateAdapter &) = delete;
  MapEvaluateAdapter &operator=(MapEvaluateAdapter &&) = delete;
  MapEvaluateAdapter &operator=(const MapEvaluateAdapter &) = delete;
  ~MapEvaluateAdapter();

 protected:
  MapEvaluateAdapter();

 public:
  /// @brief get single instance
  /// @return instance
  static MapEvaluateAdapter &GetInstance() noexcept;

  /// @brief is init flag
  /// @return true/false
  bool IsInit() const noexcept { return is_init_; }

  /// @brief python interface, use json setting to init
  /// @param jSettings serialize json string settings
  void Init(std::string const &jSettings) noexcept(false);

  /// @brief run decode and post process
  /// @param dataRaw map head data raw
  /// @return serialize json string
  std::string RunDecodeAndPost(MapHeadDataRaw const &dataRaw,
                               Eigen::Affine3d const &pose) noexcept(false);

  /// @brief is map filter enable
  /// @return
  bool IsMapFilterEnable() noexcept { return is_map_filter_enable_; }

  /// @brief
  /// @return
  std::int32_t MapMsgLength() noexcept { return map_msg_length_; }

 protected:
  /// @brief parse paramemter item
  /// @tparam T
  /// @param obj
  /// @param name
  /// @param para
  template <typename T>
  void parse_value(boost::json::object const &obj, std::string const &name,
                   T &value) noexcept(false) {
    if (!obj.contains(name)) {
      throw std::runtime_error("no " + name + " found!");
    } else {
      value = boost::json::value_to<T>(obj.at(name));
    }
  }

  /// @brief
  /// @param jv
  /// @param para
  void parse_map_decode_para(
      boost::json::value const &jv,
      autodrive::perception::MapDecoder::MapDecoderParameter
          &para) noexcept(false);

  /// @brief
  /// @param jv
  /// @param para
  void parse_map_filter_para(
      boost::json::value const &jv,
      autodrive::perception::MapFilter::MapFilterParameter
          &para) noexcept(false);

  void parse_settings(std::string const &jSettings) noexcept(false);

 private:
  /// @brief  init call once flag
  std::once_flag init_flag_;
  /// @brief is init flag
  bool is_init_;

  /// @brief map pointer to store map info
  std::shared_ptr<Map> map_ptr_;

  /// @brief map decoder parameter
  MapDecoder::MapDecoderParameter decoder_para_;
  /// @brief map decoder
  MapDecoder decoder_;

  /// @brief map filter parameter
  MapFilter::MapFilterParameter filter_para_;
  /// @brief map filter
  MapFilter filter_;

  /// @brief map msg length
  std::int32_t map_msg_length_;

  bool is_map_filter_enable_;
};
}  // namespace perception
}  // namespace autodrive

namespace boost::json {

inline autodrive::perception::MapFilter::Point2f tag_invoke(
    boost::json::value_to_tag<autodrive::perception::MapFilter::Point2f>,
    boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  return autodrive::perception::MapFilter::Point2f{
      boost::json::value_to<float>(obj.at("x")),
      boost::json::value_to<float>(obj.at("y"))};
}

inline autodrive::perception::MapFilter::ROI tag_invoke(
    boost::json::value_to_tag<autodrive::perception::MapFilter::ROI>,
    boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  return autodrive::perception::MapFilter::ROI{
      boost::json::value_to<autodrive::perception::MapFilter::Point2f>(
          obj.at("top_left")),
      boost::json::value_to<autodrive::perception::MapFilter::Point2f>(
          obj.at("bottom_right"))};
}

inline autodrive::perception::MapFilter::MatchParameter tag_invoke(
    boost::json::value_to_tag<autodrive::perception::MapFilter::MatchParameter>,
    boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  return autodrive::perception::MapFilter::MatchParameter{
      boost::json::value_to<autodrive::perception::MapFilter::ROI>(
          obj.at("roi")),
      boost::json::value_to<float>(obj.at("chamfer_distance_threshold")),
      boost::json::value_to<float>(obj.at("min_distance_threshold")),
      boost::json::value_to<float>(obj.at("max_distance_threshold"))};
}

inline autodrive::perception::MapFilter::FusionParamter tag_invoke(
    boost::json::value_to_tag<autodrive::perception::MapFilter::FusionParamter>,
    boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  return autodrive::perception::MapFilter::FusionParamter{
      boost::json::value_to<float>(obj.at("x_low_limit")),
      boost::json::value_to<float>(obj.at("threshold")),
      boost::json::value_to<float>(obj.at("history_weight"))};
}

}  // namespace boost::json

#endif  // MAP_EVALUATE_ADAPTER_H_
